#include "remote/client/client.h"
#include "remote/base/basedefines.h"
#include "remote/base/helps.h"

namespace remote {

Client::Client(boost::asio::io_service &io_service)
  : io_service_(io_service) {
}

Client::~Client() {
}

bool Client::Start(const char *addr, uint16 port) {
  BOOST_ASSERT(!control_session_);
  control_session_.reset(new ClientControlSession(io_service_, "admin"));

  control_session_->SignalConnectFailure.connect(
    boost::bind(&Client::OnConnectFailure, shared_from_this(), _1, _2));
  control_session_->SignalConnectSucceed.connect(
    boost::bind(&Client::OnConnectSucceed, shared_from_this(), _1));
  control_session_->SignalSessionMessage.connect(
    boost::bind(&Client::OnSessionMessage, shared_from_this(), _1, _2));

  return control_session_->Start(addr, port);
}

void Client::Stop() {
}

void Client::OnConnectFailure(ClientControlSession::Ptr session,
                              uint32 error_code) {
  LOG(ERROR) << "Connect Server Failure " << error_code;
}

void Client::OnConnectSucceed(ClientControlSession::Ptr session) {
  LOG(INFO) << "Connect Session Succeed " << session->session_name();
  LOG(INFO) << "Server Session Name " << session->server_name();
  GetOnlineSessions();
}

void Client::OnSessionMessage(ClientControlSession::Ptr session,
                              QueryStanza::Ptr stanza) {
  for(Tasks::iterator iter = tasks_.begin(); iter != tasks_.end(); ++iter) {
    if((*iter)->HandleStanza(stanza)) {
      break;
    }
  }
  if(stanza->type() == JSON_TYPE_CREATE_TUNNEL_PROXY) {
    ProcessCreateTunnel(stanza);
  }
}

void Client::GetOnlineSessions() {
  QueryStanza::Ptr stanza = QueryStanza::CreateQueryStanza(
                              JSON_TYPE_SERVER_GET_ONLINE_SESSION,
                              STATE_REQUESTION_STATE,
                              RandomString(DEFAULT_ID_SIZE),
                              control_session_->server_name(),
                              control_session_->session_name()
                            );
  Task::Ptr task(new Task(io_service_, stanza));
  tasks_.insert(task);
  control_session_->AsyncWrite(stanza);
}

void Client::OnOnlineSessions(Task::Ptr task,
                              QueryStanza::Ptr stanza,
                              uint32 error_code) {
  LOG(INFO) << stanza->ToString();
}


void Client::ProcessCreateTunnel(QueryStanza::Ptr stanza) {
  CreateTunnelSettings::Ptr cts(new CreateTunnelSettings);
  cts->server_addr = stanza->body()[JSON_BODY_SERVER_ADDR].asString();
  cts->server_port = stanza->body()[JSON_BODY_SERVER_PORT].asUInt();
  cts->tunnel_addr = stanza->body()[JSON_BODY_TUNELL_ADDR].asString();
  cts->tunnel_port = stanza->body()[JSON_BODY_TUNNEL_PORT].asUInt();
  cts->token_key   = stanza->body()[JSON_BODY_TOKEN_KEY].asString();
  vzconn::VzConnect::Ptr local_connect = vzconn::VzClient::ConnectTcpServer(
      io_service_,
      cts->tunnel_addr,
      cts->tunnel_port,
      boost::bind(&Client::OnTunnelConnect,
                  shared_from_this(), _1, _2, cts, stanza));
}

void Client::OnTunnelConnect(vzconn::VzConnect::Ptr connect,
                             const boost::system::error_code& err,
                             CreateTunnelSettings::Ptr cts,
                             QueryStanza::Ptr stanza) {
  if(err) {
    LOG(ERROR) << err.message();
    QueryStanza::Ptr rep_stanza = QueryStanza::CreateQueryStanza(
                                    JSON_TYPE_CREATE_TUNNEL_PROXY,
                                    TUNNEL_ERROR_CODE_FAILURE_CONNECT_TUNNEL,
                                    stanza->id(),
                                    stanza->from(),
                                    stanza->to());
    control_session_->AsyncWrite(rep_stanza);
    connect->CloseConnect();
  } else {
    vzconn::VzConnect::Ptr server_connect = vzconn::VzClient::ConnectTcpServer(
        io_service_,
        cts->server_addr,
        cts->server_port,
        boost::bind(&Client::OnServerConnect,
                    shared_from_this(), _1, _2, cts, stanza, connect));
  }
}

void Client::OnServerConnect(vzconn::VzConnect::Ptr server_connect,
                             const boost::system::error_code& err,
                             CreateTunnelSettings::Ptr cts,
                             QueryStanza::Ptr stanza,
                             vzconn::VzConnect::Ptr tunnel_connect) {
  QueryStanza::Ptr rep_stanza = QueryStanza::CreateQueryStanza(
                                  JSON_TYPE_CREATE_TUNNEL_PROXY,
                                  TUNNEL_ERROR_CODE_PROXY_CONNECT_SERVER,
                                  stanza->id(),
                                  stanza->from(),
                                  stanza->to());
  if(err) {
    LOG(ERROR) << err.message();
    rep_stanza->set_state(TUNNEL_ERROR_CODE_PROXY_CONNECT_SERVER);
    control_session_->AsyncWrite(rep_stanza);
    server_connect->CloseConnect();
    return;
  }
  rep_stanza->set_state(TUNNEL_CREATE_SUCCEED);
  control_session_->AsyncWrite(rep_stanza);

}
}